/*
 * ShapefileIO.java
 *
 * Created on October 25, 2008, 10:59 AM
 *
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.apps.rtemaster.shp;

import org.fpdev.apps.rtemaster.*;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.fpdev.core.SysProps;
import org.fpdev.core.basenet.BLink;
import org.fpdev.core.basenet.BNode;
import org.fpdev.core.basenet.LinkTypes;
import org.fpdev.core.basenet.Path;
import org.fpdev.core.transit.Route;
import org.fpdev.core.transit.Station;
import org.fpdev.core.transit.SubRoute;
import org.fpdev.core.transit.TransitPath;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.referencing.CRS;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

/**
 * Contains operations for reading from and writing to shapefiles. 
 * 
 * @author demory
 */

public class ShapefileIO {

  private RouteMaster ac_;
  private ShpImporter importer_;
  
  public ShapefileIO(RouteMaster ac) {
    ac_ = ac;
    importer_ = new ShpImporter(ac);
  }

  public ShpImporter getImporter() { return importer_; }

  //** GENERAL UTILITY METHODS **//

  public static Point2D.Double getMidpoint(File shapefile) {
    try {
      ShapefileDataStore store = new ShapefileDataStore(shapefile.toURI().toURL());
      // feature type name is defaulted to the name of shapefile (without extension)
      String name = store.getTypeNames()[0];
      FeatureSource source = store.getFeatureSource(name);
      FeatureCollection fColl = source.getFeatures();

      int count = fColl.size();

      //AttributeType[] attrs = store.getSchema().getAttributeTypes();

      FeatureIterator fIter = fColl.features();
      double xsum =0, ysum=0;

      while (fIter.hasNext()) {
        Feature f = fIter.next();
        double midX = (f.getBounds().getMinX()+f.getBounds().getMaxX())/2;
        double midY = (f.getBounds().getMinY()+f.getBounds().getMaxY())/2;
        xsum += midX;
        ysum += midY;
      }

      return new Point2D.Double(xsum/fColl.size(), ysum/fColl.size());
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  public File chooseShapeFile() {
    JFileChooser fileChooser = new JFileChooser();
    String curDir = ac_.getEngine().getProperty(SysProps.FILE_DIR);
    if(curDir != null) fileChooser.setCurrentDirectory(new File(curDir));
    
    FileNameExtensionFilter filter = new FileNameExtensionFilter("Shapefiles", "shp");
    fileChooser.setFileFilter(filter);

    //fileChooser.setFileFilter(new SimpleFileFilter("shp", "Shapefile"));

    int result = fileChooser.showOpenDialog(null);

    if (result == JFileChooser.APPROVE_OPTION) {
      ac_.getEngine().setProperty(SysProps.FILE_DIR, fileChooser.getCurrentDirectory().toString());
      return fileChooser.getSelectedFile();
    }

    return null;
  }

  //** SHAPEFILE EXPORT METHODS **//

  public void writeLinksShapefile() {
    try {

      // CREATE FILE

      String name = "fp_links";
      File file = chooseShapeFile();
      if(!file.exists()) file.createNewFile();
      
      // create the datastore
      ShapefileDataStore outStore = new ShapefileDataStore(file.toURI().toURL());

      // Tell this shapefile what type of data it will store
      SimpleFeatureType featureType = DataUtilities.createType(name, "geom:MultiLineString,fpid:Integer,fptype:Integer,fpname:String,subcount:Integer,routes:String");
      outStore.createSchema(featureType);
      
      // initialize the CRS
      String crsCode = ac_.getEngine().getDataPackage().getProperty("crsCode");
      CoordinateReferenceSystem crs = CRS.decode(crsCode);
      //System.out.println("crs: "+crs.getName());
      outStore.forceSchemaCRS(crs);

      FeatureWriter outFeatureWriter = outStore.getFeatureWriter(outStore.getTypeNames()[0], Transaction.AUTO_COMMIT);

      // POPULATE THE DATA


      GeometryFactory gf = new GeometryFactory();
      int totalItems = ac_.getEngine().getBaseNet().linkCount(), numWritten = 0, pctWritten = 0, oldPctWritten;
      for (BLink link : ac_.getEngine().getBaseNet().getLinks()) {
        if(link.getID() < 0 || !link.getScenario().isAccessibleTo(ac_.getActiveScenario())) continue;
        
        Coordinate start = new Coordinate(link.getX1(), link.getY1());
        Coordinate end = new Coordinate(link.getX2(), link.getY2());


        List<Point2D.Double> shpPts = link.getShapePointsList();
        Coordinate[] coords = new Coordinate[2 + shpPts.size()];

        coords[0] = start;
        Iterator<Point2D.Double> spIter = shpPts.iterator();
        int spi = 1;
        while (spIter.hasNext()) {
          Point2D.Double pt = spIter.next();
          coords[spi] = new Coordinate(pt.x, pt.y);
          spi++;
        }
        coords[1 + shpPts.size()] = end;

        LineString line = gf.createLineString(coords);
        LineString[] lines = new LineString[1];
        lines[0] = line;

        MultiLineString multi = gf.createMultiLineString(lines);

        SimpleFeature feature = (SimpleFeature) outFeatureWriter.next();
        feature.setDefaultGeometry(multi);
        feature.setAttribute("fpid", link.getID());
        feature.setAttribute("fptype", link.getType());
        feature.setAttribute("fpname", link.getDisplayName());
        //String routes = "";
        //Set<String> visited = new HashSet<String>();
        int subcount = 0;
        for(Path path : link.getRegisteredPaths()) {
          if(path.isTransitPath()) {
            subcount++;
            /*String rte = ((TransitPath) path).getSubRoute().getRoute().getCombinedID();
            if(!visited.contains(rte))  {
              visited.add(rte);
            }*/
          }
        }
        //if(routes.length() > 0) routes = routes.substring(0,routes.length()-1);
        feature.setAttribute("subcount", subcount);
        //feature.setAttribute("routes", routes);

        outFeatureWriter.write();

        numWritten++;
        oldPctWritten = pctWritten;
        double pctWrittenD = 100.0 * (double) numWritten / (double) totalItems;
        pctWritten = (int) pctWrittenD;
        if (pctWritten != oldPctWritten) ac_.getGUI().setProgress(pctWritten);
      }
      // WRITE TO DATASTORE

      outFeatureWriter.close();

      //store.addFeatures(collection);
      //transaction.commit();
      //transaction.close();
      ac_.getGUI().setProgress(0);
      ac_.msg("Shapefile written");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  public void writeRoutesShapefile() {
    try {

      // CREATE FILE

      String name = "fp_routes";
      File file = chooseShapeFile();
      if(!file.exists()) file.createNewFile();

      // create the datastore
      ShapefileDataStore outStore = new ShapefileDataStore(file.toURI().toURL());

      // Tell this shapefile what type of data it will store
      SimpleFeatureType featureType = DataUtilities.createType(name, "geom:MultiLineString,provid:String,provname:String,rteid:String,combid:String,name:String,type:String");
      outStore.createSchema(featureType);

      // initialize the CRS
      String crsCode = ac_.getEngine().getDataPackage().getProperty("crsCode");
      CoordinateReferenceSystem crs = CRS.decode(crsCode);
      //System.out.println("crs: "+crs.getName());
      outStore.forceSchemaCRS(crs);

      FeatureWriter outFeatureWriter = outStore.getFeatureWriter(outStore.getTypeNames()[0], Transaction.AUTO_COMMIT);

      // POPULATE THE DATA


      GeometryFactory gf = new GeometryFactory();
      int totalItems = ac_.getEngine().getBaseNet().linkCount(), numWritten = 0, pctWritten = 0, oldPctWritten;
      for(Route rte : ac_.getEngine().getRoutes().getCollection(ac_.getActiveScenario())) {

        Set<BLink> links = new HashSet<BLink>();
        Set<TransitPath.Type> types = new HashSet<TransitPath.Type>();
        for(SubRoute sub : rte.getSubRoutes()) {
          links.addAll(sub.getPath().getLinkSet());
          types.add(sub.getPath().getType());
        }

        if(links.size() == 0) continue;
        LineString[] lines = new LineString[links.size()];

        int i=0;
        for(BLink link : links) {
          Coordinate start = new Coordinate(link.getX1(), link.getY1());
          Coordinate end = new Coordinate(link.getX2(), link.getY2());

          List<Point2D.Double> shpPts = link.getShapePointsList();
          Coordinate[] coords = new Coordinate[2 + shpPts.size()];

          coords[0] = start;
          Iterator<Point2D.Double> spIter = shpPts.iterator();
          int spi = 1;
          while (spIter.hasNext()) {
            Point2D.Double pt = spIter.next();
            coords[spi] = new Coordinate(pt.x, pt.y);
            spi++;
          }

          coords[1 + shpPts.size()] = end;

          LineString line = gf.createLineString(coords);
          lines[i] = line;
          i++;
        }

        MultiLineString multi = gf.createMultiLineString(lines);

        SimpleFeature feature = (SimpleFeature) outFeatureWriter.next();
        feature.setDefaultGeometry(multi);
        feature.setAttribute("provid", rte.getProvider().getID());
        feature.setAttribute("provname", rte.getProvider().getName());
        feature.setAttribute("rteid", rte.getID());
        feature.setAttribute("combid", rte.getCombinedID());
        feature.setAttribute("name", rte.getName());

        i=0;
        String typeStr = "";
        for(TransitPath.Type type : types) {
          typeStr += type.getShortName();
          if(i < types.size()-1) typeStr += ",";
          i++;
        }
        feature.setAttribute("type", typeStr);

        outFeatureWriter.write();

        numWritten++;
        oldPctWritten = pctWritten;
        double pctWrittenD = 100.0 * (double) numWritten / (double) totalItems;
        pctWritten = (int) pctWrittenD;
        if (pctWritten != oldPctWritten) ac_.getGUI().setProgress(pctWritten);
      }
      // WRITE TO DATASTORE

      outFeatureWriter.close();

      //store.addFeatures(collection);
      //transaction.commit();
      //transaction.close();
      ac_.getGUI().setProgress(0);
      ac_.msg("Shapefile written");
    } catch (Exception e) {
      e.printStackTrace();
    }

  }

  public void writeStationsShapefile() {
    try {

      // CREATE FILE

      String name = "fp_stations";
      File file = chooseShapeFile();
      if(!file.exists()) file.createNewFile();

      // create the datastore
      ShapefileDataStore outStore = new ShapefileDataStore(file.toURI().toURL());

      // Tell this shapefile what type of data it will store
      SimpleFeatureType featureType = DataUtilities.createType(name, "geom:Point,staid:String,staname:String,metcount:Integer,lblrot:Integer,lblanchx:Float");
      outStore.createSchema(featureType);

      // initialize the CRS
      String crsCode = ac_.getEngine().getDataPackage().getProperty("crsCode");
      CoordinateReferenceSystem crs = CRS.decode(crsCode);
      //System.out.println("crs: "+crs.getName());
      outStore.forceSchemaCRS(crs);

      FeatureWriter outFeatureWriter = outStore.getFeatureWriter(outStore.getTypeNames()[0], Transaction.AUTO_COMMIT);

      // POPULATE THE DATA


      GeometryFactory gf = new GeometryFactory();
      int totalItems = ac_.getEngine().getBaseNet().linkCount(), numWritten = 0, pctWritten = 0, oldPctWritten;

      for(Station sta : ac_.getEngine().getStations().getCollection()) {
        for(BNode node : sta.getNodes()) {
          Point point = gf.createPoint(new Coordinate(node.getX(), node.getY()));

          SimpleFeature feature = (SimpleFeature) outFeatureWriter.next();
          feature.setDefaultGeometry(point);
          feature.setAttribute("staid", sta.getID());
          feature.setAttribute("staname", sta.getName());

          int metCount = 0;
          for(BLink link : ac_.getEngine().getBaseNet().incidentLinks(node)) {
            switch(link.getType()) {
              case LinkTypes.TR_METRO: metCount++; break;
            }
          }
          feature.setAttribute("metcount", metCount);

          outFeatureWriter.write();

          numWritten++;
          oldPctWritten = pctWritten;
          double pctWrittenD = 100.0 * (double) numWritten / (double) totalItems;
          pctWritten = (int) pctWrittenD;
          if (pctWritten != oldPctWritten) ac_.getGUI().setProgress(pctWritten);
        }
      }

      // WRITE TO DATASTORE

      outFeatureWriter.close();

      //store.addFeatures(collection);
      //transaction.commit();
      //transaction.close();
      ac_.getGUI().setProgress(0);
      ac_.msg("Shapefile written");
    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}
